/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 1999-2002 The Apache Software Foundation.  All rights 
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:  
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Xerces" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written 
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation and was
 * originally based on software copyright (c) 1999, International
 * Business Machines, Inc., http://www.apache.org.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

package org.apache.xerces.util;

import org.apache.xerces.xni.grammars.Grammar;
import org.apache.xerces.xni.grammars.XMLGrammarPool;
import org.apache.xerces.xni.grammars.XMLGrammarDescription;

import org.apache.xerces.impl.dtd.XMLDTDDescription;
//import org.apache.xerces.impl.xs.XSDDescription;

import java.util.Hashtable;
import java.util.Enumeration;

/**
 * Stores grammars in a pool associated to a specific key. This grammar pool
 * implementation stores two types of grammars: those keyed by the root element
 * name, and those keyed by the grammar's target namespace.
 *
 * This is the default implementation of the GrammarPool interface.  
 * As we move forward, this will become more function-rich and robust.
 *
 * @author Jeffrey Rodriguez, IBM
 * @author Andy Clark, IBM
 * @author Neil Graham, IBM
 * @author Pavani Mukthipudi, Sun Microsystems
 * @author Neeraj Bajaj, SUN Microsystems
 *
 * @version $Id: XMLGrammarPoolImpl.java,v 1.1.1.1 2002/10/31 15:40:38 pettys Exp $
 */
public class XMLGrammarPoolImpl implements XMLGrammarPool {

    // 
    // Constants
    // 
    
    /** Default size. */
    protected static final int TABLE_SIZE = 11;

    //
    // Data
    //
    
    /** Grammars. */
    protected Entry[] fGrammars = null;
    
    // whether this pool is locked
    protected boolean fPoolIsLocked;

    private static final boolean DEBUG = false ;
	
    //
    // Constructors
    //

    /** Constructs a grammar pool with a default number of buckets. */
    public XMLGrammarPoolImpl() {
    	fGrammars = new Entry[TABLE_SIZE];
        fPoolIsLocked = false;
    } // <init>()
    
    /** Constructs a grammar pool with a specified number of buckets. */
    public XMLGrammarPoolImpl(int initialCapacity) {
        fGrammars = new Entry[initialCapacity];
        fPoolIsLocked = false;
    }

    //
    // XMLGrammarPool methods
    //
    
    /* <p> Retrieve the initial known set of grammars. This method is
     * called by a validator before the validation starts. The application 
     * can provide an initial set of grammars available to the current 
     * validation attempt. </p>
     * 
     * @param grammarType The type of the grammar, from the
     *  		  <code>org.apache.xerces.xni.grammars.XMLGrammarDescription</code> 
     *  		  interface.
     * @return 		  The set of grammars the validator may put in its "bucket"
     */
    public Grammar [] retrieveInitialGrammarSet (String grammarType) {
        synchronized (fGrammars) {
            int grammarSize = fGrammars.length ;
            Grammar [] tempGrammars = new Grammar[grammarSize];
            int pos = 0;
            for (int i = 0; i < grammarSize; i++) {
                for (Entry e = fGrammars[i]; e != null; e = e.next) {
                    if (e.desc.getGrammarType().equals(grammarType)) {
                        tempGrammars[pos++] = e.grammar;
                    }
                }
            }
            Grammar[] toReturn = new Grammar[pos];
            System.arraycopy(tempGrammars, 0, toReturn, 0, pos);
            return toReturn;
        }
    } // retrieveInitialGrammarSet (String): Grammar[]

    /* <p> Return the final set of grammars that the validator ended up
     * with. This method is called after the validation finishes. The 
     * application may then choose to cache some of the returned grammars.</p>
     * <p>In this implementation, we make our choice based on whether this object
     * is "locked"--that is, whether the application has instructed
     * us not to accept any new grammars.</p>
     *
     * @param grammarType The type of the grammars being returned;
     * @param grammars 	  An array containing the set of grammars being
     *  		  returned; order is not significant.
     */
    public void cacheGrammars(String grammarType, Grammar[] grammars) {
        if(!fPoolIsLocked) {
    	    for (int i = 0; i < grammars.length; i++) {
                if(DEBUG) {
    	            System.out.println("CACHED GRAMMAR " + (i+1) ) ;
    	            Grammar temp = grammars[i] ;
    	            //print(temp.getGrammarDescription());
                }
    	        putGrammar(grammars[i]);
            }
    	}
    } // cacheGrammars(String, Grammar[]);
    
    /* <p> This method requests that the application retrieve a grammar
     * corresponding to the given GrammarIdentifier from its cache.
     * If it cannot do so it must return null; the parser will then
     * call the EntityResolver. </p>
     * <strong>An application must not call its EntityResolver itself 
     * from this method; this may result in infinite recursions.</strong>
     * 
     * This implementation chooses to use the root element name to identify a DTD grammar
     * and the target namespace to identify a Schema grammar.
     * 
     * @param desc The description of the Grammar being requested.
     * @return     The Grammar corresponding to this description or null if
     *  	   no such Grammar is known.
     */
    public Grammar retrieveGrammar(XMLGrammarDescription desc) {
        if(DEBUG){
            System.out.println("RETRIEVING GRAMMAR FROM THE APPLICATION WITH FOLLOWING DESCRIPTION :");
            //print(desc);
        }
        return getGrammar(desc);
    } // retrieveGrammar(XMLGrammarDescription):  Grammar

    //
    // Public methods
    //

    /**
     * Puts the specified grammar into the grammar pool and associates it to
     * its root element name or its target namespace.
     *
     * @param grammar The Grammar.
     */
    public void putGrammar(Grammar grammar) {
        if(!fPoolIsLocked) {
            synchronized (fGrammars) {
                XMLGrammarDescription desc = grammar.getGrammarDescription();
                int hash = hashCode(desc);
                int index = (hash & 0x7FFFFFFF) % fGrammars.length;
                for (Entry entry = fGrammars[index]; entry != null; entry = entry.next) {
                    if (entry.hash == hash && equals(entry.desc, desc)) {
                        entry.grammar = grammar;
                        return;
                    }
                }
                // create a new entry
                Entry entry = new Entry(hash, desc, grammar, fGrammars[index]);
                fGrammars[index] = entry;
            }
        }
    } // putGrammar(Grammar)

    /**
     * Returns the grammar associated to the specified grammar description.
     * Currently, the root element name is used as the key for DTD grammars 
     * and the target namespace  is used as the key for Schema grammars.
     *
     * @param desc The Grammar Description.
     */
    public Grammar getGrammar(XMLGrammarDescription desc) {
        synchronized (fGrammars) {
            int hash = hashCode(desc);
	    int index = (hash & 0x7FFFFFFF) % fGrammars.length;
	    for (Entry entry = fGrammars[index] ; entry != null ; entry = entry.next) {
	        if ((entry.hash == hash) && equals(entry.desc, desc)) {
	            return entry.grammar;
	        }
	    }
	    return null;
	}
    } // getGrammar(XMLGrammarDescription):Grammar

    /**
     * Removes the grammar associated to the specified grammar description from the
     * grammar pool and returns the removed grammar. Currently, the root element name 
     * is used as the key for DTD grammars and the target namespace  is used 
     * as the key for Schema grammars.
     * 
     * @param desc The Grammar Description.
     * @return     The removed grammar.
     */
    public Grammar removeGrammar(XMLGrammarDescription desc) {
        synchronized (fGrammars) {
    	    int hash = hashCode(desc);
	    int index = (hash & 0x7FFFFFFF) % fGrammars.length;
	    for (Entry entry = fGrammars[index], prev = null ; entry != null ; prev = entry, entry = entry.next) {
	        if ((entry.hash == hash) && equals(entry.desc, desc)) {
	            if (prev != null) {
                        prev.next = entry.next;
		    } 
		    else {
		        fGrammars[index] = entry.next;
		    }
	    	    Grammar tempGrammar = entry.grammar;
	    	    entry.grammar = null;
	            return tempGrammar;
	        }
	    }
	    return null;
        }
    } // removeGrammar(XMLGrammarDescription):Grammar

    /**
     * Returns true if the grammar pool contains a grammar associated
     * to the specified grammar description. Currently, the root element name 
     * is used as the key for DTD grammars and the target namespace  is used 
     * as the key for Schema grammars.
     *
     * @param desc The Grammar Description.
     */
    public boolean containsGrammar(XMLGrammarDescription desc) {
        synchronized (fGrammars) {
    	    int hash = hashCode(desc);
	    int index = (hash & 0x7FFFFFFF) % fGrammars.length;
	    for (Entry entry = fGrammars[index] ; entry != null ; entry = entry.next) {
	        if ((entry.hash == hash) && equals(entry.desc, desc)) {
	            return true;
	        }
	    }
	    return false;
	}
    } // containsGrammar(XMLGrammarDescription):boolean

    /* <p> Sets this grammar pool to a "locked" state--i.e.,
     * no new grammars will be added until it is "unlocked".
     */
    public void lockPool() {
        fPoolIsLocked = true;
    } // lockPool()

    /* <p> Sets this grammar pool to an "unlocked" state--i.e.,
     * new grammars will be added when putGrammar or cacheGrammars 
     * are called.
     */
    public void unlockPool() {
        fPoolIsLocked = false;
    } // unlockPool()

    /* 
     * <p>This method clears the pool-i.e., removes references
     * to all the grammars in it.</p>
     */
    public void clear() {
        for (int i=0; i<fGrammars.length; i++) {
            if(fGrammars[i] != null) {
                fGrammars[i].clear();
                fGrammars[i] = null;
            }
        }
    } // clear()

    /**
     * This method checks whether two grammars are the same. Currently, we compare 
     * the root element names for DTD grammars and the target namespaces for Schema grammars.
     * The application can override this behaviour and add its own logic.
     *
     * @param gDesc1 The grammar description
     * @param gDesc2 The grammar description of the grammar to be compared to
     * @return       True if the grammars are equal, otherwise false
     */
    public boolean equals(XMLGrammarDescription desc1, XMLGrammarDescription desc2) {
        return desc1.equals(desc2);
    }
    
    /**
     * Returns the hash code value for the given grammar description.
     *
     * @param desc The grammar description
     * @return     The hash code value
     */
    public int hashCode(XMLGrammarDescription desc) {
        return desc.hashCode();
    }
    
    /**
     * This class is a grammar pool entry. Each entry acts as a node
     * in a linked list.
     */
    protected static final class Entry {
        public int hash;
        public XMLGrammarDescription desc;
        public Grammar grammar;
        public Entry next;
	
        protected Entry(int hash, XMLGrammarDescription desc, Grammar grammar, Entry next) {
            this.hash = hash;
            this.desc = desc;
            this.grammar = grammar;
            this.next = next;
        }

        // clear this entry; useful to promote garbage collection
        // since reduces reference count of objects to be destroyed
        protected void clear () {
            desc = null;
            grammar = null;
            if(next != null) {
                next.clear();
                next = null;
            }
        } // clear()
    } // class Entry
    
    /* For DTD build we can't import here XSDDescription. Thus, this method is commented out.. */
    /* public void print(XMLGrammarDescription description){
    	if(description.getGrammarType().equals(XMLGrammarDescription.XML_DTD)){
    	
    	}
    	else if(description.getGrammarType().equals(XMLGrammarDescription.XML_SCHEMA)){
    		XSDDescription schema = (XSDDescription)description ;
    		System.out.println("Context = " + schema.getContextType());
    		System.out.println("TargetNamespace = " + schema.getTargetNamespace());
    		String [] temp = schema.getLocationHints();
    		
    		for (int i = 0 ; (temp != null && i < temp.length) ; i++){
    			System.out.println("LocationHint " + i + " = "+ temp[i]);
    		}
    		    		
    		System.out.println("Triggering Component = " + schema.getTriggeringComponent());
    		System.out.println("EnclosingElementName =" + schema.getEnclosingElementName());
    	
    	}
    
    }//print
    */
    
} // class XMLGrammarPoolImpl
